<!DOCTYPE html>
<html>

  <head>
  <meta charset="utf-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">

  <title>安卓自定义View进阶-事件分发机制详解</title>
  <meta name="description" content="Android 事件分发机制详解，在上一篇文章 事件分发机制原理 中简要分析了一下事件分发机制的原理，原理是十分简单的，一句话就能总结：责任链模式，事件层层传递，直到被消费。而本篇的核心问题是 正确理解在实际场景中事件分发机制的作用。会涉及到源码，但不是主角。">
  <meta name="author" content="GcsSloop">
  <meta name="keywords" content="安卓,事件分发,事件分发机制,事件分发机制原理,事件分发机制详解,责任链模式, 自定义View详解, 自定义控件, 安卓, Android, CustomView, GcsSloop">
  <meta name="关键字" content="安卓,事件分发,事件分发机制,事件分发机制原理,事件分发机制详解,责任链模式, 自定义View详解, 自定义控件, 安卓, Android, CustomView, GcsSloop">
  

  <meta name="twitter:card" content="summary">
  <meta name="twitter:title" content="安卓自定义View进阶-事件分发机制详解">
  <meta name="twitter:description" content="Android 事件分发机制详解，在上一篇文章 事件分发机制原理 中简要分析了一下事件分发机制的原理，原理是十分简单的，一句话就能总结：责任链模式，事件层层传递，直到被消费。而本篇的核心问题是 正确理解在实际场景中事件分发机制的作用。会涉及到源码，但不是主角。">
  <meta name="twitter:keywords" content="安卓,事件分发,事件分发机制,事件分发机制原理,事件分发机制详解,责任链模式, 自定义View详解, 自定义控件, 安卓, Android, CustomView, GcsSloop">
  
  <meta property="og:type" content="article">
  <meta property="og:title" content="安卓自定义View进阶-事件分发机制详解">
  <meta property="og:description" content="Android 事件分发机制详解，在上一篇文章 事件分发机制原理 中简要分析了一下事件分发机制的原理，原理是十分简单的，一句话就能总结：责任链模式，事件层层传递，直到被消费。而本篇的核心问题是 正确理解在实际场景中事件分发机制的作用。会涉及到源码，但不是主角。">
  <meta name="og:keywords" content="安卓,事件分发,事件分发机制,事件分发机制原理,事件分发机制详解,责任链模式, 自定义View详解, 自定义控件, 安卓, Android, CustomView, GcsSloop">

  <meta name="theme-color" content="#343434">
  
  <link rel="icon" type="image/png" href="https://raw.githubusercontent.com/GcsSloop/gcssloop.github.io/master/assets/siteinfo/favicon.png" />
  <link href="https://raw.githubusercontent.com/GcsSloop/gcssloop.github.io/master/assets/siteinfo/favicon.png" rel="shortcut icon" type="image/png">
  
  <link href="//netdna.bootstrapcdn.com/font-awesome/4.4.0/css/font-awesome.min.css" rel="stylesheet">
  <link rel="stylesheet" href="/css/main.css">

  <link rel="canonical" href="http://www.gcssloop.com/customview/dispatch-touchevent-source">
  <link rel="alternate" type="application/rss+xml" title="GcsSloop" href="http://www.gcssloop.com/feed.xml">
  
  <meta name="google-site-verification" content="Z_g58PkzRAyBMxkqrcDdWrTBK8oOWM-7rUHauhLNF2E" />
  <meta name="baidu-site-verification" content="kUtTXCKaZs" />
  <meta name="baidu-site-verification" content="6DuDv3aaJX" />
  
  <!--阅读次数统计-->
  <script async src="https://busuanzi.ibruce.info/busuanzi/2.3/busuanzi.pure.mini.js"> </script>
  
  <!--Fuck Weixin and Baidu-->
  <meta http-equiv="Cache-Control" content="no-transform">
  <meta http-equiv=”Cache-Control” content=”no-siteapp” />
  <meta name="applicable-device" content="pc,mobile">
  <meta name="HandheldFriendly" content="true"/>

  <!-- Google Ad -->
  <script async src="//pagead2.googlesyndication.com/pagead/js/adsbygoogle.js"></script>
  <script>
    (adsbygoogle = window.adsbygoogle || []).push({
      google_ad_client: "ca-pub-2767831356529421",
      enable_page_level_ads: true
    });
  </script>

</head>


  <body>

    <span class="mobile btn-mobile-menu">
        <i class="fa fa-list btn-mobile-menu__icon"></i>
        <i class="fa fa-angle-up btn-mobile-close__icon hidden"></i>
    </span>
    
    <header class="panel-cover panel-cover--collapsed" style="background-image: url('/assets/siteinfo/background-cover.jpg')">
  <div class="panel-main">

    <div class="panel-main__inner panel-inverted">
    <div class="panel-main__content">

        <a href="/#blog" title="前往 GcsSloop 的主页" class="blog-button"><img src="/assets/siteinfo/avatar.jpg" width="80" alt="GcsSloop logo" class="panel-cover__logo logo" /></a>
        <h1 class="panel-cover__title panel-title"><a href="/#blog" title="link to homepage for GcsSloop" class="blog-button">GcsSloop</a></h1>

        
        <span class="panel-cover__subtitle panel-subtitle">Just do IT later.</span>
        
        <hr class="panel-cover__divider" />
        <p class="panel-cover__description">嗨，我是 GcsSloop，一名来自2.5次元的魔法师，Android自定义View系列文章作者，非著名程序员。</p>
        <hr class="panel-cover__divider panel-cover__divider--secondary" />
        
        
        <p class="panel-cover__description">欢迎来到我的魔法世界!</p>
        
        
        <div class="navigation-wrapper">
          <div>
            <nav class="cover-navigation cover-navigation--primary">
              <ul class="navigation">
                <li class="navigation__item"><a href="/#blog" title="访问博客" class="blog-button">博客</a></li>
                
                  
                    <li class="navigation__item"><a href="https://github.com/GcsSloop" target="_blank" title="GcsSloop's GitHub">GitHub</a></li>
                  
                  
                
                  
                    <li class="navigation__item"><a href="/timeline" title="博客目录">目录</a></li>
                  
                  
                
                  
                    <li class="navigation__item"><a href="https://xiaozhuanlan.com/u/GcsSloop" target="_blank" title="小专栏">专栏</a></li>
                  
                  
                
                  
                    <li class="navigation__item"><a href="/category/customview" title="自定义View教程目录">自定义控件</a></li>
                  
                  
                
                  
                    <li class="navigation__item"><a href="/friends" title="友链">友链</a></li>
                  
                  
                
              </ul>
            </nav>
          </div>
          
          <div><nav class="cover-navigation navigation--social">
  <ul class="navigation">

  
  <!-- Weibo -->
  <li class="navigation__item">
    <a href="http://weibo.com/GcsSloop" title="@GcsSloop 的微博" target="_blank">
      <i class='social fa fa-weibo'></i>
      <span class="label">Weibo</span>
    </a>
  </li>
  

  
  <!-- Github -->
  <li class="navigation__item">
    <a href="https://github.com/GcsSloop" title="@GcsSloop 的 Github" target="_blank">
      <i class='social fa fa-github'></i>
      <span class="label">Github</span>
    </a>
  </li>
  
  
  
  <!-- Twitter -->
  <li class="navigation__item">
    <a href="http://twitter.com/GcsSloop" title="@GcsSloop" target="_blank">
      <i class='social fa fa-twitter'></i>
      <span class="label">Twitter</span>
    </a>
  </li>
  

    

  

  
  <!-- RSS -->
  <li class="navigation__item">
    <a href="/feed.xml" rel="author" title="RSS" target="_blank">
      <i class='social fa fa-rss'></i>
      <span class="label">RSS</span>
    </a>
  </li>
  

  
  <!-- Email -->
  <li class="navigation__item">
    <a href="mailto:GcsSloop@gmail.com" title="发邮件给我">
      <i class='social fa fa-envelope'></i>
      <span class="label">Email</span>
    </a>
  </li>
  

  
  <!-- Copyright -->
  <li class="navigation__item">
    <a href="http://choosealicense.online" title="选择版权"  target="_blank">
      <i class="social fa fa-copyright"></i>
      <span class="label">版权</span>
    </a>
  </li>
  
  
  </ul>
</nav>
</div>
        </div>
      </div>
    </div>
    
    
    <div class="panel-cover--overlay cover-slate"></div>
    
  </div>
</header>


    <div class="content-wrapper">
        <div class="content-wrapper__inner">
            <article class="post-container post-container--single" itemscope itemtype="http://schema.org/BlogPosting">
  <header class="post-header">
    <div class="post-meta" style="font-size:.8em">
      <time datetime="2016-10-14 00:00:00 +0800" itemprop="datePublished" class="post-meta__date date">2016-10-14</time> &#8226; <span class="post-meta__tags tags">Camera,MotionEvent</span> &#8226; View <span id="busuanzi_value_page_pv"></span> times.
</span>
    </div>
    <h1 class="post-title">安卓自定义View进阶-事件分发机制详解</h1>
  </header>

  <section class="post">
    <p>Android 事件分发机制详解，在上一篇文章 <a href="http://www.gcssloop.com/customview/dispatch-touchevent-theory" title="事件分发机制原理－GcsSloop">事件分发机制原理</a> 中简要分析了一下事件分发机制的原理，原理是十分简单的，一句话就能总结：<strong>责任链模式，事件层层传递，直到被消费。</strong> 虽然原理简单，但是随着 Android 不断的发展，实际运用场景也越来越复杂，所以想要彻底玩转事件分发机制还需要一定技巧，本篇事件分发机制详解将带大家了解 …</p>

<blockquote>
  <p><strong>你以为我接下来要讲源码？</strong><br />
我就不按套路，所有的源码都是为了适应具体的应用场景而写的，只要能够理解运用场景，理解源码也就十分简单了。所以本篇的核心问题是：<strong>正确理解在实际场景中事件分发机制的作用。</strong> 会涉及到源码，但不是主角。</p>
</blockquote>

<p><strong>注意：本文中所有源码分析部分均基于 API23(Android 6.0) 版本，由于安卓系统源码改变很多，可能与之前版本有所不同，但基本流程都是一致的。</strong></p>

<h2 id="常见事件">常见事件</h2>

<p>既然是事件分发，总要有事件才能分发吧，所以我们先了解一下常见的几种事件。</p>

<p>根据面向对象思想，事件被封装成 MotionEvent 对象，由于本篇重点不在于此，所以只会涉及到几个与手指触摸相关的常见事件:</p>

<table>
  <thead>
    <tr>
      <th>事件</th>
      <th>简介</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>ACTION_DOWN</td>
      <td>手指 <strong>初次接触到屏幕</strong> 时触发。</td>
    </tr>
    <tr>
      <td>ACTION_MOVE</td>
      <td>手指 <strong>在屏幕上滑动</strong> 时触发，会会多次触发。</td>
    </tr>
    <tr>
      <td>ACTION_UP</td>
      <td>手指 <strong>离开屏幕</strong> 时触发。</td>
    </tr>
    <tr>
      <td>ACTION_CANCEL</td>
      <td>事件 <strong>被上层拦截</strong> 时触发。</td>
    </tr>
  </tbody>
</table>

<p>对于单指触控来说，一次简单的交互流程是这样的:</p>

<p><strong>手指落下(ACTION_DOWN) －&gt; 移动(ACTION_MOVE) －&gt; 离开(ACTION_UP)</strong></p>

<blockquote>
  <ul>
    <li>本次事例中 ACTION_MOVE 有多次触发。</li>
    <li>如果仅仅是单击(手指按下再抬起)，不会触发 ACTION_MOVE。</li>
  </ul>
</blockquote>

<p><img src="http://gcsblog.oss-cn-shanghai.aliyuncs.com/blog/2019-04-29-071714.gif?gcssloop" alt="" /></p>

<h2 id="事件分发拦截与消费">事件分发、拦截与消费</h2>

<p>关于这一部分内容，上一篇文章 <a href="http://www.gcssloop.com/customview/dispatch-touchevent-theory" title="事件分发机制原理－GcsSloop">事件分发机制原理</a> 已经将流程整理的比较清楚了，本文会深入细节来研究这些内容。之所以分开讲，是为了防止大家被细节所迷惑而忽略了整体逻辑。</p>

<blockquote>
  <p><code class="highlighter-rouge">√</code> 表示有该方法。</p>

  <p><code class="highlighter-rouge">X</code> 表示没有该方法。</p>
</blockquote>

<table>
  <thead>
    <tr>
      <th style="text-align: center">类型</th>
      <th style="text-align: center">相关方法</th>
      <th style="text-align: center">ViewGroup</th>
      <th style="text-align: center">View</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td style="text-align: center">事件分发</td>
      <td style="text-align: center">dispatchTouchEvent</td>
      <td style="text-align: center">√</td>
      <td style="text-align: center">√</td>
    </tr>
    <tr>
      <td style="text-align: center">事件拦截</td>
      <td style="text-align: center">onInterceptTouchEvent</td>
      <td style="text-align: center">√</td>
      <td style="text-align: center">X</td>
    </tr>
    <tr>
      <td style="text-align: center">事件消费</td>
      <td style="text-align: center">onTouchEvent</td>
      <td style="text-align: center">√</td>
      <td style="text-align: center">√</td>
    </tr>
  </tbody>
</table>

<h3 id="view-相关">View 相关</h3>

<p><code class="highlighter-rouge">dispatchTouchEvent</code> 是事件分发机制中的核心，所有的事件调度都归它管。不过我细看表格， ViewGroup 有 dispatchTouchEvent 也就算了，毕竟人家有一堆 ChildView 需要管理，但为啥 View 也有？这就引出了我们的第一个疑问。</p>

<h4 id="q-为什么-view-会有-dispatchtouchevent-">Q: 为什么 View 会有 dispatchTouchEvent ?</h4>

<p>A: 我们知道 View 可以注册很多事件监听器，例如：单击事件(onClick)、长按事件(onLongClick)、触摸事件(onTouch)，并且View自身也有 onTouchEvent 方法，那么问题来了，这么多与事件相关的方法应该由谁管理？毋庸置疑就是 <code class="highlighter-rouge">dispatchTouchEvent</code>，所以 View 也会有事件分发。</p>

<p>相信看到这里很多小伙伴会产生第二个疑问，View 有这么多事件监听器，到底哪个先执行？</p>

<h4 id="q-与-view-事件相关的各个方法调用顺序是怎样的">Q: 与 View 事件相关的各个方法调用顺序是怎样的？</h4>

<p>A: <strong>如果不去看源码，想一下让自己设计会怎样？</strong></p>

<ul>
  <li>单击事件(onClickListener) 需要两个两个事件(ACTION_DOWN 和 ACTION_UP )才能触发，如果先分配给onClick判断，等它判断完，用户手指已经离开屏幕，黄花菜都凉了，定然造成 View 无法响应其他事件，应该最后调用。(最后)</li>
  <li>长按事件(onLongClickListener) 同理，也是需要长时间等待才能出结果，肯定不能排到前面，但因为不需要ACTION_UP，应该排在 onClick 前面。(onLongClickListener &gt; onClickListener)</li>
  <li>触摸事件(onTouchListener) 如果用户注册了触摸事件，说明用户要自己处理触摸事件了，这个应该排在最前面。(最前)</li>
  <li>View自身处理(onTouchEvent) 提供了一种默认的处理方式，如果用户已经处理好了，也就不需要了，所以应该排在 onTouchListener 后面。(onTouchListener &gt; onTouchEvent)</li>
</ul>

<p><strong>所以事件的调度顺序应该是 <code class="highlighter-rouge">onTouchListener &gt; onTouchEvent &gt; onLongClickListener &gt; onClickListener</code></strong>。</p>

<p><img src="http://gcsblog.oss-cn-shanghai.aliyuncs.com/blog/2019-04-29-071717.jpg?gcssloop" alt="" /></p>

<p>下面我们来看一下实际测试结果:</p>

<blockquote>
  <p>手指按下，不移动，稍等片刻再抬起。</p>
</blockquote>

<div class="language-shell highlighter-rouge"><pre class="highlight"><code><span class="o">[</span>Listener ]: onTouchListener      ACTION_DOWN
<span class="o">[</span>GcsView  ]: onTouchEvent         ACTION_DOWN
<span class="o">[</span>Listener ]: onLongClickListener  
<span class="o">[</span>Listener ]: onTouchListener      ACTION_UP
<span class="o">[</span>GcsView  ]: onTouchEvent         ACTION_UP
<span class="o">[</span>Listener ]: onClickListener      
</code></pre>
</div>

<p>可以看到，测试结果也支持我们猜测的结论，因为长按 onLongClickListener 不需要 ACTION_UP 所以会在 ACTION_DOWN 之后就触发。</p>

<p>接下来就看一下源码是怎么设计的(省略了大量无关代码)：</p>

<div class="language-java highlighter-rouge"><pre class="highlight"><code><span class="kd">public</span> <span class="kt">boolean</span> <span class="nf">dispatchTouchEvent</span><span class="o">(</span><span class="n">MotionEvent</span> <span class="n">event</span><span class="o">)</span> <span class="o">{</span>
    <span class="o">...</span>
    <span class="kt">boolean</span> <span class="n">result</span> <span class="o">=</span> <span class="kc">false</span><span class="o">;</span>	<span class="c1">// result 为返回值，主要作用是告诉调用者事件是否已经被消费。</span>
    <span class="k">if</span> <span class="o">(</span><span class="n">onFilterTouchEventForSecurity</span><span class="o">(</span><span class="n">event</span><span class="o">))</span> <span class="o">{</span>
        <span class="n">ListenerInfo</span> <span class="n">li</span> <span class="o">=</span> <span class="n">mListenerInfo</span><span class="o">;</span>
        <span class="cm">/** 
         * 如果设置了OnTouchListener，并且当前 View 可点击，就调用监听器的 onTouch 方法，
         * 如果 onTouch 方法返回值为 true，就设置 result 为 true。
         */</span>
        <span class="k">if</span> <span class="o">(</span><span class="n">li</span> <span class="o">!=</span> <span class="kc">null</span> <span class="o">&amp;&amp;</span> <span class="n">li</span><span class="o">.</span><span class="na">mOnTouchListener</span> <span class="o">!=</span> <span class="kc">null</span>
                <span class="o">&amp;&amp;</span> <span class="o">(</span><span class="n">mViewFlags</span> <span class="o">&amp;</span> <span class="n">ENABLED_MASK</span><span class="o">)</span> <span class="o">==</span> <span class="n">ENABLED</span>
                <span class="o">&amp;&amp;</span> <span class="n">li</span><span class="o">.</span><span class="na">mOnTouchListener</span><span class="o">.</span><span class="na">onTouch</span><span class="o">(</span><span class="k">this</span><span class="o">,</span> <span class="n">event</span><span class="o">))</span> <span class="o">{</span>
            <span class="n">result</span> <span class="o">=</span> <span class="kc">true</span><span class="o">;</span>
        <span class="o">}</span>
      
        <span class="cm">/** 
         * 如果 result 为 false，则调用自身的 onTouchEvent。
         * 如果 onTouchEvent 返回值为 true，则设置 result 为 true。
         */</span>
        <span class="k">if</span> <span class="o">(!</span><span class="n">result</span> <span class="o">&amp;&amp;</span> <span class="n">onTouchEvent</span><span class="o">(</span><span class="n">event</span><span class="o">))</span> <span class="o">{</span>
            <span class="n">result</span> <span class="o">=</span> <span class="kc">true</span><span class="o">;</span>
        <span class="o">}</span>
    <span class="o">}</span>
    <span class="o">...</span>
    <span class="k">return</span> <span class="n">result</span><span class="o">;</span>
<span class="o">}</span>
</code></pre>
</div>

<blockquote>
  <p><strong>如果觉得源码还是太长，那么用伪代码实现应当是这样的(省略若干安全判断)，简单粗暴:</strong></p>

  <div class="language-java highlighter-rouge"><pre class="highlight"><code><span class="kd">public</span> <span class="kt">boolean</span> <span class="nf">dispatchTouchEvent</span><span class="o">(</span><span class="n">MotionEvent</span> <span class="n">event</span><span class="o">)</span> <span class="o">{</span>
  <span class="k">if</span> <span class="o">(</span><span class="n">mOnTouchListener</span><span class="o">.</span><span class="na">onTouch</span><span class="o">(</span><span class="k">this</span><span class="o">,</span> <span class="n">event</span><span class="o">))</span> <span class="o">{</span>
      <span class="k">return</span> <span class="kc">true</span><span class="o">;</span>
  <span class="o">}</span> <span class="k">else</span> <span class="k">if</span> <span class="o">(</span><span class="n">onTouchEvent</span><span class="o">(</span><span class="n">event</span><span class="o">))</span> <span class="o">{</span>
      <span class="k">return</span> <span class="kc">true</span><span class="o">;</span>
  <span class="o">}</span>
  <span class="k">return</span> <span class="kc">false</span><span class="o">;</span>
<span class="o">}</span>
</code></pre>
  </div>
</blockquote>

<p>正当你沉迷在源码的”精妙”逻辑的时候，你可能没发现有两个东西失踪了，等回过神来，定睛一看，哎呦妈呀，<strong>OnClick 和 OnLongClick 去哪里了？</strong></p>

<p>不要担心，OnClick 和 OnLongClick 的具体调用位置在 <strong>onTouchEvent</strong> 中，看源码(同样省略大量无关代码):</p>

<div class="language-java highlighter-rouge"><pre class="highlight"><code><span class="kd">public</span> <span class="kt">boolean</span> <span class="nf">onTouchEvent</span><span class="o">(</span><span class="n">MotionEvent</span> <span class="n">event</span><span class="o">)</span> <span class="o">{</span>
    <span class="o">...</span>
    <span class="kd">final</span> <span class="kt">int</span> <span class="n">action</span> <span class="o">=</span> <span class="n">event</span><span class="o">.</span><span class="na">getAction</span><span class="o">();</span>
  	<span class="c1">// 检查各种 clickable</span>
    <span class="k">if</span> <span class="o">(((</span><span class="n">viewFlags</span> <span class="o">&amp;</span> <span class="n">CLICKABLE</span><span class="o">)</span> <span class="o">==</span> <span class="n">CLICKABLE</span> <span class="o">||</span>
            <span class="o">(</span><span class="n">viewFlags</span> <span class="o">&amp;</span> <span class="n">LONG_CLICKABLE</span><span class="o">)</span> <span class="o">==</span> <span class="n">LONG_CLICKABLE</span><span class="o">)</span> <span class="o">||</span>
            <span class="o">(</span><span class="n">viewFlags</span> <span class="o">&amp;</span> <span class="n">CONTEXT_CLICKABLE</span><span class="o">)</span> <span class="o">==</span> <span class="n">CONTEXT_CLICKABLE</span><span class="o">)</span> <span class="o">{</span>
        <span class="k">switch</span> <span class="o">(</span><span class="n">action</span><span class="o">)</span> <span class="o">{</span>
            <span class="k">case</span> <span class="n">MotionEvent</span><span class="o">.</span><span class="na">ACTION_UP</span><span class="o">:</span>
                <span class="o">...</span>
                <span class="n">removeLongPressCallback</span><span class="o">();</span>  <span class="c1">// 移除长按</span>
                <span class="o">...</span>
                <span class="n">performClick</span><span class="o">();</span>             <span class="c1">// 检查单击</span>
                <span class="o">...</span>
                <span class="k">break</span><span class="o">;</span>
            <span class="k">case</span> <span class="n">MotionEvent</span><span class="o">.</span><span class="na">ACTION_DOWN</span><span class="o">:</span>
                <span class="o">...</span>
                <span class="n">checkForLongClick</span><span class="o">(</span><span class="mi">0</span><span class="o">);</span>       <span class="c1">// 检测长按</span>
                <span class="o">...</span>
                <span class="k">break</span><span class="o">;</span>
            <span class="o">...</span>
        <span class="o">}</span>
        <span class="k">return</span> <span class="kc">true</span><span class="o">;</span>                        <span class="c1">// ◀︎表示事件被消费</span>
    <span class="o">}</span>
    <span class="k">return</span> <span class="kc">false</span><span class="o">;</span>
<span class="o">}</span>
</code></pre>
</div>

<blockquote>
  <p><strong>注意了，第一个重点要出现了(敲黑板)!</strong></p>

  <p><img src="http://gcsblog.oss-cn-shanghai.aliyuncs.com/blog/2019-04-29-071718.jpg?gcssloop" alt="" /></p>

  <p><strong>注意上面代码中存在一个 <code class="highlighter-rouge">return true;</code> 并且是只要 View 可点击就返回 true，就表示事件被消费了。</strong></p>

  <p>举个栗子: I have a <strong>RelativeLayout</strong>，I have a <strong>View</strong>，Ugh，<strong>RelativeLayout - View</strong></p>

  <div class="language-xml highlighter-rouge"><pre class="highlight"><code><span class="nt">&lt;RelativeLayout</span>
    <span class="na">android:background=</span><span class="s">"#CCC"</span>
    <span class="na">android:id=</span><span class="s">"@+id/layout"</span>
    <span class="na">android:onClick=</span><span class="s">"myClick"</span>
    <span class="na">android:layout_width=</span><span class="s">"200dp"</span>
    <span class="na">android:layout_height=</span><span class="s">"200dp"</span><span class="nt">&gt;</span>
    <span class="nt">&lt;View</span>
        <span class="na">android:clickable=</span><span class="s">"true"</span>
        <span class="na">android:layout_width=</span><span class="s">"200dp"</span>
        <span class="na">android:layout_height=</span><span class="s">"200dp"</span> <span class="nt">/&gt;</span>
<span class="nt">&lt;/RelativeLayout&gt;</span>
</code></pre>
  </div>

  <p>现在你有了一个 <strong>RelativeLayout - View</strong> 你开开心心的为 RelativeLayout 设置了一个点击事件<code class="highlighter-rouge">myClick</code>，然而你会发现不论怎么点都不会接收到信息，仔细一看，发现内部的 View 有一个属性 <code class="highlighter-rouge">android:clickable="true"</code> 正是这个看似不起眼的属性把事件给消费掉了，由此我们可以得出如下结论:<br />
<strong>1. 不论 View 自身是否注册点击事件，只要 View 是可点击的就会消费事件。</strong><br />
<strong>2. 事件是否被消费由返回值决定，true 表示消费，false 表示不消费，与是否使用了事件无关。</strong></p>
</blockquote>

<p>关于 View 的事件分发先说这么多，下面我们来看一下 ViewGroup 的事件分发。</p>

<h3 id="viewgroup-相关">ViewGroup 相关</h3>

<p><strong>ViewGroup(通常是各种Layout) 的事件分发相对来说就要麻烦一些，因为 ViewGroup 不仅要考虑自身，还要考虑各种 ChildView，一旦处理不好就容易引起各种事件冲突，正所谓养儿方知父母难啊。</strong></p>

<h4 id="viewgroup-的事件分发流程又是如何的呢">VIewGroup 的事件分发流程又是如何的呢？</h4>

<p>上一篇文章 <a href="http://www.gcssloop.com/customview/dispatch-touchevent-theory" title="事件分发机制原理－GcsSloop">事件分发机制原理</a> 中我们了解到事件是通过ViewGroup一层一层传递的，最终传递给 View，ViewGroup 要比它的 ChildView 先拿到事件，并且有权决定是否告诉要告诉 ChildView。在默认的情况下 ViewGroup 事件分发流程是这样的。</p>

<ul>
  <li>1.判断自身是否需要(询问 onInterceptTouchEvent 是否拦截)，如果需要，调用自己的 onTouchEvent。</li>
  <li>2.自身不需要或者不确定，则询问 ChildView ，一般来说是调用手指触摸位置的 ChildView。</li>
  <li>3.如果子 ChildView 不需要则调用自身的 onTouchEvent。</li>
</ul>

<p>用伪代码应该是这样的:</p>

<div class="language-java highlighter-rouge"><pre class="highlight"><code><span class="kd">public</span> <span class="kt">boolean</span> <span class="nf">dispatchTouchEvent</span><span class="o">(</span><span class="n">MotionEvent</span> <span class="n">ev</span><span class="o">)</span> <span class="o">{</span>
    <span class="kt">boolean</span> <span class="n">result</span> <span class="o">=</span> <span class="kc">false</span><span class="o">;</span>             <span class="c1">// 默认状态为没有消费过</span>

    <span class="k">if</span> <span class="o">(!</span><span class="n">onInterceptTouchEvent</span><span class="o">(</span><span class="n">ev</span><span class="o">))</span> <span class="o">{</span>   <span class="c1">// 如果没有拦截交给子View</span>
        <span class="n">result</span> <span class="o">=</span> <span class="n">child</span><span class="o">.</span><span class="na">dispatchTouchEvent</span><span class="o">(</span><span class="n">ev</span><span class="o">);</span>
    <span class="o">}</span>

    <span class="k">if</span> <span class="o">(!</span><span class="n">result</span><span class="o">)</span> <span class="o">{</span>                      <span class="c1">// 如果事件没有被消费,询问自身onTouchEvent</span>
        <span class="n">result</span> <span class="o">=</span> <span class="n">onTouchEvent</span><span class="o">(</span><span class="n">ev</span><span class="o">);</span>
    <span class="o">}</span>

    <span class="k">return</span> <span class="n">result</span><span class="o">;</span>
<span class="o">}</span>
</code></pre>
</div>

<p><strong>有人看到这里可能会有疑问，我看过源码，ViewGroup 的 <code class="highlighter-rouge">dispatchTouchEvent</code> 可有二百多行呢，你弄这几行就想忽悠我，别以为我读书少。</strong></p>

<p>当然了，上述源码是不完善的，还有很多问题是没有解决的，例如:</p>

<h5 id="1-viewgroup-中可能有多个-childview如何判断应该分配给哪一个">1. ViewGroup 中可能有多个 ChildView，如何判断应该分配给哪一个？</h5>

<p>这个很容易，就是把所有的 ChildView 遍历一遍，如果手指触摸的点在 ChildView 区域内就分发给这个View。</p>

<h5 id="2-当该点的-childview-有重叠时应该如何分配">2. 当该点的 ChildView 有重叠时应该如何分配？</h5>

<p>当 ChildView 重叠时，<strong>一般会分配给显示在最上面的 ChildView</strong>。<br />
如何判断哪个是显示在最上面的呢？后面加载的一般会覆盖掉之前的，所以<strong>显示在最上面的是最后加载的</strong>。</p>

<p>如下:</p>

<div class="language-xml highlighter-rouge"><pre class="highlight"><code><span class="nt">&lt;RelativeLayout</span> <span class="na">xmlns:android=</span><span class="s">"http://schemas.android.com/apk/res/android"</span>
    <span class="na">xmlns:tools=</span><span class="s">"http://schemas.android.com/tools"</span> 
    <span class="na">android:id=</span><span class="s">"@+id/activity_main"</span>
    <span class="na">android:layout_width=</span><span class="s">"match_parent"</span> 
    <span class="na">android:layout_height=</span><span class="s">"match_parent"</span>
    <span class="na">tools:context=</span><span class="s">"com.gcssloop.viewtest.MainActivity"</span><span class="nt">&gt;</span>
    <span class="nt">&lt;View</span>
        <span class="na">android:id=</span><span class="s">"@+id/view1"</span>
        <span class="na">android:background=</span><span class="s">"#E4A07B"</span>
        <span class="na">android:layout_width=</span><span class="s">"200dp"</span>
        <span class="na">android:layout_height=</span><span class="s">"200dp"</span><span class="nt">/&gt;</span>
    <span class="nt">&lt;View</span>
        <span class="na">android:id=</span><span class="s">"@+id/view2"</span>
        <span class="na">android:layout_margin=</span><span class="s">"100dp"</span>
        <span class="na">android:background=</span><span class="s">"#BDDA66"</span>
        <span class="na">android:layout_width=</span><span class="s">"200dp"</span>
        <span class="na">android:layout_height=</span><span class="s">"200dp"</span><span class="nt">/&gt;</span>
<span class="nt">&lt;/RelativeLayout&gt;</span>
</code></pre>
</div>

<p><img src="http://gcsblog.oss-cn-shanghai.aliyuncs.com/blog/2019-04-29-71719.jpg?gcssloop" alt="" /></p>

<p>当手指点击有重叠区域时，分如下几种情况:</p>

<ol>
  <li>只有 View1 可点击时，事件将会分配给 View1，即使被 View2 遮挡，这一部分仍是 View1 的可点击区域。</li>
  <li>只有 View2 可点击时，事件将会分配给 View2。</li>
  <li>View1 和 View2 均可点击时，事件会分配给后加载的 View2，View2 将事件消费掉，View1接收不到事件。</li>
</ol>

<p><strong>注意:</strong></p>

<ul>
  <li>上面说的是可点击，可点击包括很多种情况，只要你给View注册了 <strong>onClickListener、onLongClickListener、OnContextClickListener</strong> 其中的任何一个监听器或者设置了 <strong>android:clickable=”true”</strong> 就代表这个 View 是可点击的。<br />
另外，某些 View 默认就是可点击的，例如，Button，CheckBox 等。</li>
  <li>给 View 注册 OnTouchListener 不会影响 View 的可点击状态。即使给 View 注册 OnTouchListener ，<strong>只要不返回 true 就不会消费事件</strong>。</li>
</ul>

<h5 id="3-viewgroup-和-childview-同时注册了事件监听器onclick等哪个会执行">3. ViewGroup 和 ChildView 同时注册了事件监听器(onClick等)，哪个会执行?</h5>

<p>事件优先给 ChildView，会被 ChildView消费掉，ViewGroup 不会响应。</p>

<h5 id="4-所有事件都应该被同一-view-消费">4. 所有事件都应该被同一 View 消费</h5>

<p>在上面的例子中我们分析后可以了解到，同一次点击事件只能被一个 View 消费，这是为什呢？主要是为了防止事件响应混乱，如果再一次完整的事件中分别将不同的事件分配给了不同的 View 容易造成事件响应混乱。</p>

<blockquote>
  <p>View 中 onClick 事件需要同时接收到 ACTION_DOWN 和 ACTION_UP 才能触发，如果分配给了不同的 View，那么 onClick 将无法被正确触发。</p>
</blockquote>

<p><strong>安卓为了保证所有的事件都是被一个 View 消费的，对第一次的事件( ACTION_DOWN )进行了特殊判断，View 只有消费了 ACTION_DOWN 事件，才能接收到后续的事件(可点击控件会默认消费所有事件)，并且会将后续所有事件传递过来，不会再传递给其他 View，除非上层 View 进行了拦截。</strong><br />
<strong>如果上层 View 拦截了当前正在处理的事件，会收到一个 ACTION_CANCEL，表示当前事件已经结束，后续事件不会再传递过来。</strong></p>

<p><strong>源码:</strong></p>

<blockquote>
  <p>其实如果能够理解上面的内容，不看源码也能非常顺利的使用事件分发，但源码中能挖掘出更多的内容。</p>
</blockquote>

<div class="language-java highlighter-rouge"><pre class="highlight"><code><span class="kd">public</span> <span class="kt">boolean</span> <span class="nf">dispatchTouchEvent</span><span class="o">(</span><span class="n">MotionEvent</span> <span class="n">ev</span><span class="o">)</span> <span class="o">{</span>
  	<span class="c1">// 调试用</span>
    <span class="k">if</span> <span class="o">(</span><span class="n">mInputEventConsistencyVerifier</span> <span class="o">!=</span> <span class="kc">null</span><span class="o">)</span> <span class="o">{</span>
        <span class="n">mInputEventConsistencyVerifier</span><span class="o">.</span><span class="na">onTouchEvent</span><span class="o">(</span><span class="n">ev</span><span class="o">,</span> <span class="mi">1</span><span class="o">);</span>
    <span class="o">}</span>

  	<span class="c1">// 判断事件是否是针对可访问的焦点视图(很晚才添加的内容，个人猜测和屏幕辅助相关，方便盲人等使用设备)</span>
    <span class="k">if</span> <span class="o">(</span><span class="n">ev</span><span class="o">.</span><span class="na">isTargetAccessibilityFocus</span><span class="o">()</span> <span class="o">&amp;&amp;</span> <span class="n">isAccessibilityFocusedViewOrHost</span><span class="o">())</span> <span class="o">{</span>
        <span class="n">ev</span><span class="o">.</span><span class="na">setTargetAccessibilityFocus</span><span class="o">(</span><span class="kc">false</span><span class="o">);</span>
    <span class="o">}</span>

    <span class="kt">boolean</span> <span class="n">handled</span> <span class="o">=</span> <span class="kc">false</span><span class="o">;</span>
    <span class="k">if</span> <span class="o">(</span><span class="n">onFilterTouchEventForSecurity</span><span class="o">(</span><span class="n">ev</span><span class="o">))</span> <span class="o">{</span>
        <span class="kd">final</span> <span class="kt">int</span> <span class="n">action</span> <span class="o">=</span> <span class="n">ev</span><span class="o">.</span><span class="na">getAction</span><span class="o">();</span>
        <span class="kd">final</span> <span class="kt">int</span> <span class="n">actionMasked</span> <span class="o">=</span> <span class="n">action</span> <span class="o">&amp;</span> <span class="n">MotionEvent</span><span class="o">.</span><span class="na">ACTION_MASK</span><span class="o">;</span>

        <span class="c1">// 处理第一次ACTION_DOWN.</span>
        <span class="k">if</span> <span class="o">(</span><span class="n">actionMasked</span> <span class="o">==</span> <span class="n">MotionEvent</span><span class="o">.</span><span class="na">ACTION_DOWN</span><span class="o">)</span> <span class="o">{</span>
            <span class="c1">// 清除之前所有的状态</span>
            <span class="n">cancelAndClearTouchTargets</span><span class="o">(</span><span class="n">ev</span><span class="o">);</span>
            <span class="n">resetTouchState</span><span class="o">();</span>
        <span class="o">}</span>

        <span class="c1">// 检查是否需要拦截.</span>
        <span class="kd">final</span> <span class="kt">boolean</span> <span class="n">intercepted</span><span class="o">;</span>
        <span class="k">if</span> <span class="o">(</span><span class="n">actionMasked</span> <span class="o">==</span> <span class="n">MotionEvent</span><span class="o">.</span><span class="na">ACTION_DOWN</span> <span class="o">||</span> <span class="n">mFirstTouchTarget</span> <span class="o">!=</span> <span class="kc">null</span><span class="o">)</span> <span class="o">{</span>
            <span class="kd">final</span> <span class="kt">boolean</span> <span class="n">disallowIntercept</span> <span class="o">=</span> <span class="o">(</span><span class="n">mGroupFlags</span> <span class="o">&amp;</span> <span class="n">FLAG_DISALLOW_INTERCEPT</span><span class="o">)</span> <span class="o">!=</span> <span class="mi">0</span><span class="o">;</span>
            <span class="k">if</span> <span class="o">(!</span><span class="n">disallowIntercept</span><span class="o">)</span> <span class="o">{</span>
                <span class="n">intercepted</span> <span class="o">=</span> <span class="n">onInterceptTouchEvent</span><span class="o">(</span><span class="n">ev</span><span class="o">);</span>	<span class="c1">// 询问是否拦截</span>
                <span class="n">ev</span><span class="o">.</span><span class="na">setAction</span><span class="o">(</span><span class="n">action</span><span class="o">);</span> 						<span class="c1">// 恢复操作，防止被更改</span>
            <span class="o">}</span> <span class="k">else</span> <span class="o">{</span>
                <span class="n">intercepted</span> <span class="o">=</span> <span class="kc">false</span><span class="o">;</span>
            <span class="o">}</span>
        <span class="o">}</span> <span class="k">else</span> <span class="o">{</span>
          	<span class="c1">// 没有目标来处理该事件，而且也不是一个新的事件事件(ACTION_DOWN), 进行拦截。</span>
            <span class="n">intercepted</span> <span class="o">=</span> <span class="kc">true</span><span class="o">;</span>
        <span class="o">}</span>

      	<span class="c1">// 判断事件是否是针对可访问的焦点视图</span>
        <span class="k">if</span> <span class="o">(</span><span class="n">intercepted</span> <span class="o">||</span> <span class="n">mFirstTouchTarget</span> <span class="o">!=</span> <span class="kc">null</span><span class="o">)</span> <span class="o">{</span>
            <span class="n">ev</span><span class="o">.</span><span class="na">setTargetAccessibilityFocus</span><span class="o">(</span><span class="kc">false</span><span class="o">);</span>
        <span class="o">}</span>

        <span class="c1">// 检查事件是否被取消(ACTION_CANCEL).</span>
        <span class="kd">final</span> <span class="kt">boolean</span> <span class="n">canceled</span> <span class="o">=</span> <span class="n">resetCancelNextUpFlag</span><span class="o">(</span><span class="k">this</span><span class="o">)</span>
                <span class="o">||</span> <span class="n">actionMasked</span> <span class="o">==</span> <span class="n">MotionEvent</span><span class="o">.</span><span class="na">ACTION_CANCEL</span><span class="o">;</span>

        <span class="kd">final</span> <span class="kt">boolean</span> <span class="n">split</span> <span class="o">=</span> <span class="o">(</span><span class="n">mGroupFlags</span> <span class="o">&amp;</span> <span class="n">FLAG_SPLIT_MOTION_EVENTS</span><span class="o">)</span> <span class="o">!=</span> <span class="mi">0</span><span class="o">;</span>
        <span class="n">TouchTarget</span> <span class="n">newTouchTarget</span> <span class="o">=</span> <span class="kc">null</span><span class="o">;</span>
        <span class="kt">boolean</span> <span class="n">alreadyDispatchedToNewTouchTarget</span> <span class="o">=</span> <span class="kc">false</span><span class="o">;</span>
      	
      	<span class="c1">// 如果没有取消也没有被拦截	(进入事件分发)</span>
        <span class="k">if</span> <span class="o">(!</span><span class="n">canceled</span> <span class="o">&amp;&amp;</span> <span class="o">!</span><span class="n">intercepted</span><span class="o">)</span> <span class="o">{</span>

            <span class="c1">// 如果事件是针对可访问性焦点视图，我们将其提供给具有可访问性焦点的视图。</span>
          	<span class="c1">// 如果它不处理它，我们清除该标志并像往常一样将事件分派给所有的 ChildView。 </span>
            <span class="c1">// 我们检测并避免保持这种状态，因为这些事非常罕见。</span>
            <span class="n">View</span> <span class="n">childWithAccessibilityFocus</span> <span class="o">=</span> <span class="n">ev</span><span class="o">.</span><span class="na">isTargetAccessibilityFocus</span><span class="o">()</span>
                    <span class="o">?</span> <span class="n">findChildWithAccessibilityFocus</span><span class="o">()</span> <span class="o">:</span> <span class="kc">null</span><span class="o">;</span>

            <span class="k">if</span> <span class="o">(</span><span class="n">actionMasked</span> <span class="o">==</span> <span class="n">MotionEvent</span><span class="o">.</span><span class="na">ACTION_DOWN</span>
                    <span class="o">||</span> <span class="o">(</span><span class="n">split</span> <span class="o">&amp;&amp;</span> <span class="n">actionMasked</span> <span class="o">==</span> <span class="n">MotionEvent</span><span class="o">.</span><span class="na">ACTION_POINTER_DOWN</span><span class="o">)</span>
                    <span class="o">||</span> <span class="n">actionMasked</span> <span class="o">==</span> <span class="n">MotionEvent</span><span class="o">.</span><span class="na">ACTION_HOVER_MOVE</span><span class="o">)</span> <span class="o">{</span>
                <span class="kd">final</span> <span class="kt">int</span> <span class="n">actionIndex</span> <span class="o">=</span> <span class="n">ev</span><span class="o">.</span><span class="na">getActionIndex</span><span class="o">();</span>
                <span class="kd">final</span> <span class="kt">int</span> <span class="n">idBitsToAssign</span> <span class="o">=</span> <span class="n">split</span> <span class="o">?</span> <span class="mi">1</span> <span class="o">&lt;&lt;</span> <span class="n">ev</span><span class="o">.</span><span class="na">getPointerId</span><span class="o">(</span><span class="n">actionIndex</span><span class="o">)</span>
                        <span class="o">:</span> <span class="n">TouchTarget</span><span class="o">.</span><span class="na">ALL_POINTER_IDS</span><span class="o">;</span>

                <span class="c1">// 清除此指针ID的早期触摸目标，防止不同步。</span>
                <span class="n">removePointersFromTouchTargets</span><span class="o">(</span><span class="n">idBitsToAssign</span><span class="o">);</span>

                <span class="kd">final</span> <span class="kt">int</span> <span class="n">childrenCount</span> <span class="o">=</span> <span class="n">mChildrenCount</span><span class="o">;</span>
                <span class="k">if</span> <span class="o">(</span><span class="n">newTouchTarget</span> <span class="o">==</span> <span class="kc">null</span> <span class="o">&amp;&amp;</span> <span class="n">childrenCount</span> <span class="o">!=</span> <span class="mi">0</span><span class="o">)</span> <span class="o">{</span>
                    <span class="kd">final</span> <span class="kt">float</span> <span class="n">x</span> <span class="o">=</span> <span class="n">ev</span><span class="o">.</span><span class="na">getX</span><span class="o">(</span><span class="n">actionIndex</span><span class="o">);</span>	<span class="c1">// 获取触摸位置坐标</span>
                    <span class="kd">final</span> <span class="kt">float</span> <span class="n">y</span> <span class="o">=</span> <span class="n">ev</span><span class="o">.</span><span class="na">getY</span><span class="o">(</span><span class="n">actionIndex</span><span class="o">);</span>
                    <span class="c1">// 查找可以接受事件的 ChildView</span>
                    <span class="kd">final</span> <span class="n">ArrayList</span><span class="o">&lt;</span><span class="n">View</span><span class="o">&gt;</span> <span class="n">preorderedList</span> <span class="o">=</span> <span class="n">buildOrderedChildList</span><span class="o">();</span>
                    <span class="kd">final</span> <span class="kt">boolean</span> <span class="n">customOrder</span> <span class="o">=</span> <span class="n">preorderedList</span> <span class="o">==</span> <span class="kc">null</span>
                            <span class="o">&amp;&amp;</span> <span class="n">isChildrenDrawingOrderEnabled</span><span class="o">();</span>
                    <span class="kd">final</span> <span class="n">View</span><span class="o">[]</span> <span class="n">children</span> <span class="o">=</span> <span class="n">mChildren</span><span class="o">;</span>
                  	<span class="c1">// ▼注意，从最后向前扫描</span>
                    <span class="k">for</span> <span class="o">(</span><span class="kt">int</span> <span class="n">i</span> <span class="o">=</span> <span class="n">childrenCount</span> <span class="o">-</span> <span class="mi">1</span><span class="o">;</span> <span class="n">i</span> <span class="o">&gt;=</span> <span class="mi">0</span><span class="o">;</span> <span class="n">i</span><span class="o">--)</span> <span class="o">{</span>
                        <span class="kd">final</span> <span class="kt">int</span> <span class="n">childIndex</span> <span class="o">=</span> <span class="n">customOrder</span>
                                <span class="o">?</span> <span class="n">getChildDrawingOrder</span><span class="o">(</span><span class="n">childrenCount</span><span class="o">,</span> <span class="n">i</span><span class="o">)</span> <span class="o">:</span> <span class="n">i</span><span class="o">;</span>
                        <span class="kd">final</span> <span class="n">View</span> <span class="n">child</span> <span class="o">=</span> <span class="o">(</span><span class="n">preorderedList</span> <span class="o">==</span> <span class="kc">null</span><span class="o">)</span>
                                <span class="o">?</span> <span class="n">children</span><span class="o">[</span><span class="n">childIndex</span><span class="o">]</span> <span class="o">:</span> <span class="n">preorderedList</span><span class="o">.</span><span class="na">get</span><span class="o">(</span><span class="n">childIndex</span><span class="o">);</span>

                        <span class="c1">// 如果有一个视图具有可访问性焦点，我们希望它首先获取事件，</span>
                      	<span class="c1">// 如果不处理，我们将执行正常的分派。 </span>
                      	<span class="c1">// 尽管这可能会分发两次，但它能保证在给定的时间内更安全的执行。</span>
                        <span class="k">if</span> <span class="o">(</span><span class="n">childWithAccessibilityFocus</span> <span class="o">!=</span> <span class="kc">null</span><span class="o">)</span> <span class="o">{</span>
                            <span class="k">if</span> <span class="o">(</span><span class="n">childWithAccessibilityFocus</span> <span class="o">!=</span> <span class="n">child</span><span class="o">)</span> <span class="o">{</span>
                                <span class="k">continue</span><span class="o">;</span>
                            <span class="o">}</span>
                            <span class="n">childWithAccessibilityFocus</span> <span class="o">=</span> <span class="kc">null</span><span class="o">;</span>
                            <span class="n">i</span> <span class="o">=</span> <span class="n">childrenCount</span> <span class="o">-</span> <span class="mi">1</span><span class="o">;</span>
                        <span class="o">}</span>

                      	<span class="c1">// 检查View是否允许接受事件(即处于显示状态(VISIBLE)或者正在播放动画)</span>
                      	<span class="c1">// 检查触摸位置是否在View区域内</span>
                        <span class="k">if</span> <span class="o">(!</span><span class="n">canViewReceivePointerEvents</span><span class="o">(</span><span class="n">child</span><span class="o">)</span>
                                <span class="o">||</span> <span class="o">!</span><span class="n">isTransformedTouchPointInView</span><span class="o">(</span><span class="n">x</span><span class="o">,</span> <span class="n">y</span><span class="o">,</span> <span class="n">child</span><span class="o">,</span> <span class="kc">null</span><span class="o">))</span> <span class="o">{</span>
                            <span class="n">ev</span><span class="o">.</span><span class="na">setTargetAccessibilityFocus</span><span class="o">(</span><span class="kc">false</span><span class="o">);</span>
                            <span class="k">continue</span><span class="o">;</span>
                        <span class="o">}</span>

                      	<span class="c1">// getTouchTarget 中判断了 child 是否包含在 mFirstTouchTarget 中</span>
                      	<span class="c1">// 如果有返回 target，如果没有返回 null </span>
                        <span class="n">newTouchTarget</span> <span class="o">=</span> <span class="n">getTouchTarget</span><span class="o">(</span><span class="n">child</span><span class="o">);</span>
                        <span class="k">if</span> <span class="o">(</span><span class="n">newTouchTarget</span> <span class="o">!=</span> <span class="kc">null</span><span class="o">)</span> <span class="o">{</span>
                            <span class="c1">// ChildView 已经准备好接受在其区域内的事件。</span>
                            <span class="n">newTouchTarget</span><span class="o">.</span><span class="na">pointerIdBits</span> <span class="o">|=</span> <span class="n">idBitsToAssign</span><span class="o">;</span>
                            <span class="k">break</span><span class="o">;</span>	<span class="c1">// ◀︎已经找到目标View，跳出循环</span>
                        <span class="o">}</span>

                        <span class="n">resetCancelNextUpFlag</span><span class="o">(</span><span class="n">child</span><span class="o">);</span>
                        <span class="k">if</span> <span class="o">(</span><span class="n">dispatchTransformedTouchEvent</span><span class="o">(</span><span class="n">ev</span><span class="o">,</span> <span class="kc">false</span><span class="o">,</span> <span class="n">child</span><span class="o">,</span> <span class="n">idBitsToAssign</span><span class="o">))</span> <span class="o">{</span>
                            <span class="n">mLastTouchDownTime</span> <span class="o">=</span> <span class="n">ev</span><span class="o">.</span><span class="na">getDownTime</span><span class="o">();</span>
                            <span class="k">if</span> <span class="o">(</span><span class="n">preorderedList</span> <span class="o">!=</span> <span class="kc">null</span><span class="o">)</span> <span class="o">{</span>
                                <span class="k">for</span> <span class="o">(</span><span class="kt">int</span> <span class="n">j</span> <span class="o">=</span> <span class="mi">0</span><span class="o">;</span> <span class="n">j</span> <span class="o">&lt;</span> <span class="n">childrenCount</span><span class="o">;</span> <span class="n">j</span><span class="o">++)</span> <span class="o">{</span>
                                    <span class="k">if</span> <span class="o">(</span><span class="n">children</span><span class="o">[</span><span class="n">childIndex</span><span class="o">]</span> <span class="o">==</span> <span class="n">mChildren</span><span class="o">[</span><span class="n">j</span><span class="o">])</span> <span class="o">{</span>
                                        <span class="n">mLastTouchDownIndex</span> <span class="o">=</span> <span class="n">j</span><span class="o">;</span>
                                        <span class="k">break</span><span class="o">;</span>
                                    <span class="o">}</span>
                                <span class="o">}</span>
                            <span class="o">}</span> <span class="k">else</span> <span class="o">{</span>
                                <span class="n">mLastTouchDownIndex</span> <span class="o">=</span> <span class="n">childIndex</span><span class="o">;</span>
                            <span class="o">}</span>
                            <span class="n">mLastTouchDownX</span> <span class="o">=</span> <span class="n">ev</span><span class="o">.</span><span class="na">getX</span><span class="o">();</span>
                            <span class="n">mLastTouchDownY</span> <span class="o">=</span> <span class="n">ev</span><span class="o">.</span><span class="na">getY</span><span class="o">();</span>
                            <span class="n">newTouchTarget</span> <span class="o">=</span> <span class="n">addTouchTarget</span><span class="o">(</span><span class="n">child</span><span class="o">,</span> <span class="n">idBitsToAssign</span><span class="o">);</span>
                            <span class="n">alreadyDispatchedToNewTouchTarget</span> <span class="o">=</span> <span class="kc">true</span><span class="o">;</span>
                            <span class="k">break</span><span class="o">;</span>
                        <span class="o">}</span>
                      
                        <span class="n">ev</span><span class="o">.</span><span class="na">setTargetAccessibilityFocus</span><span class="o">(</span><span class="kc">false</span><span class="o">);</span>
                    <span class="o">}</span>
                    <span class="k">if</span> <span class="o">(</span><span class="n">preorderedList</span> <span class="o">!=</span> <span class="kc">null</span><span class="o">)</span> <span class="n">preorderedList</span><span class="o">.</span><span class="na">clear</span><span class="o">();</span>
                <span class="o">}</span>

                <span class="k">if</span> <span class="o">(</span><span class="n">newTouchTarget</span> <span class="o">==</span> <span class="kc">null</span> <span class="o">&amp;&amp;</span> <span class="n">mFirstTouchTarget</span> <span class="o">!=</span> <span class="kc">null</span><span class="o">)</span> <span class="o">{</span>
                    <span class="c1">// 没有找到 ChildView 接收事件</span>
                    <span class="n">newTouchTarget</span> <span class="o">=</span> <span class="n">mFirstTouchTarget</span><span class="o">;</span>
                    <span class="k">while</span> <span class="o">(</span><span class="n">newTouchTarget</span><span class="o">.</span><span class="na">next</span> <span class="o">!=</span> <span class="kc">null</span><span class="o">)</span> <span class="o">{</span>
                        <span class="n">newTouchTarget</span> <span class="o">=</span> <span class="n">newTouchTarget</span><span class="o">.</span><span class="na">next</span><span class="o">;</span>
                    <span class="o">}</span>
                    <span class="n">newTouchTarget</span><span class="o">.</span><span class="na">pointerIdBits</span> <span class="o">|=</span> <span class="n">idBitsToAssign</span><span class="o">;</span>
                <span class="o">}</span>
            <span class="o">}</span>
        <span class="o">}</span>

        <span class="c1">// 分发 TouchTarget</span>
        <span class="k">if</span> <span class="o">(</span><span class="n">mFirstTouchTarget</span> <span class="o">==</span> <span class="kc">null</span><span class="o">)</span> <span class="o">{</span>
            <span class="c1">// 没有 TouchTarget，将当前 ViewGroup 当作普通的 View 处理。</span>
            <span class="n">handled</span> <span class="o">=</span> <span class="n">dispatchTransformedTouchEvent</span><span class="o">(</span><span class="n">ev</span><span class="o">,</span> <span class="n">canceled</span><span class="o">,</span> <span class="kc">null</span><span class="o">,</span>
                    <span class="n">TouchTarget</span><span class="o">.</span><span class="na">ALL_POINTER_IDS</span><span class="o">);</span>
        <span class="o">}</span> <span class="k">else</span> <span class="o">{</span>
            <span class="c1">// 分发TouchTarget，如果我们已经分发过，则避免分配给新的目标。 </span>
          	<span class="c1">// 如有必要，取消分发。</span>
            <span class="n">TouchTarget</span> <span class="n">predecessor</span> <span class="o">=</span> <span class="kc">null</span><span class="o">;</span>
            <span class="n">TouchTarget</span> <span class="n">target</span> <span class="o">=</span> <span class="n">mFirstTouchTarget</span><span class="o">;</span>
            <span class="k">while</span> <span class="o">(</span><span class="n">target</span> <span class="o">!=</span> <span class="kc">null</span><span class="o">)</span> <span class="o">{</span>
                <span class="kd">final</span> <span class="n">TouchTarget</span> <span class="n">next</span> <span class="o">=</span> <span class="n">target</span><span class="o">.</span><span class="na">next</span><span class="o">;</span>
                <span class="k">if</span> <span class="o">(</span><span class="n">alreadyDispatchedToNewTouchTarget</span> <span class="o">&amp;&amp;</span> <span class="n">target</span> <span class="o">==</span> <span class="n">newTouchTarget</span><span class="o">)</span> <span class="o">{</span>
                    <span class="n">handled</span> <span class="o">=</span> <span class="kc">true</span><span class="o">;</span>
                <span class="o">}</span> <span class="k">else</span> <span class="o">{</span>
                    <span class="kd">final</span> <span class="kt">boolean</span> <span class="n">cancelChild</span> <span class="o">=</span> <span class="n">resetCancelNextUpFlag</span><span class="o">(</span><span class="n">target</span><span class="o">.</span><span class="na">child</span><span class="o">)</span>
                            <span class="o">||</span> <span class="n">intercepted</span><span class="o">;</span>
                    <span class="k">if</span> <span class="o">(</span><span class="n">dispatchTransformedTouchEvent</span><span class="o">(</span><span class="n">ev</span><span class="o">,</span> <span class="n">cancelChild</span><span class="o">,</span>
                            <span class="n">target</span><span class="o">.</span><span class="na">child</span><span class="o">,</span> <span class="n">target</span><span class="o">.</span><span class="na">pointerIdBits</span><span class="o">))</span> <span class="o">{</span>
                        <span class="n">handled</span> <span class="o">=</span> <span class="kc">true</span><span class="o">;</span>
                    <span class="o">}</span>
                    <span class="k">if</span> <span class="o">(</span><span class="n">cancelChild</span><span class="o">)</span> <span class="o">{</span>
                        <span class="k">if</span> <span class="o">(</span><span class="n">predecessor</span> <span class="o">==</span> <span class="kc">null</span><span class="o">)</span> <span class="o">{</span>
                            <span class="n">mFirstTouchTarget</span> <span class="o">=</span> <span class="n">next</span><span class="o">;</span>
                        <span class="o">}</span> <span class="k">else</span> <span class="o">{</span>
                            <span class="n">predecessor</span><span class="o">.</span><span class="na">next</span> <span class="o">=</span> <span class="n">next</span><span class="o">;</span>
                        <span class="o">}</span>
                        <span class="n">target</span><span class="o">.</span><span class="na">recycle</span><span class="o">();</span>
                        <span class="n">target</span> <span class="o">=</span> <span class="n">next</span><span class="o">;</span>
                        <span class="k">continue</span><span class="o">;</span>
                    <span class="o">}</span>
                <span class="o">}</span>
                <span class="n">predecessor</span> <span class="o">=</span> <span class="n">target</span><span class="o">;</span>
                <span class="n">target</span> <span class="o">=</span> <span class="n">next</span><span class="o">;</span>
            <span class="o">}</span>
        <span class="o">}</span>

        <span class="c1">// 如果需要，更新指针的触摸目标列表或取消。</span>
        <span class="k">if</span> <span class="o">(</span><span class="n">canceled</span>
                <span class="o">||</span> <span class="n">actionMasked</span> <span class="o">==</span> <span class="n">MotionEvent</span><span class="o">.</span><span class="na">ACTION_UP</span>
                <span class="o">||</span> <span class="n">actionMasked</span> <span class="o">==</span> <span class="n">MotionEvent</span><span class="o">.</span><span class="na">ACTION_HOVER_MOVE</span><span class="o">)</span> <span class="o">{</span>
            <span class="n">resetTouchState</span><span class="o">();</span>
        <span class="o">}</span> <span class="k">else</span> <span class="k">if</span> <span class="o">(</span><span class="n">split</span> <span class="o">&amp;&amp;</span> <span class="n">actionMasked</span> <span class="o">==</span> <span class="n">MotionEvent</span><span class="o">.</span><span class="na">ACTION_POINTER_UP</span><span class="o">)</span> <span class="o">{</span>
            <span class="kd">final</span> <span class="kt">int</span> <span class="n">actionIndex</span> <span class="o">=</span> <span class="n">ev</span><span class="o">.</span><span class="na">getActionIndex</span><span class="o">();</span>
            <span class="kd">final</span> <span class="kt">int</span> <span class="n">idBitsToRemove</span> <span class="o">=</span> <span class="mi">1</span> <span class="o">&lt;&lt;</span> <span class="n">ev</span><span class="o">.</span><span class="na">getPointerId</span><span class="o">(</span><span class="n">actionIndex</span><span class="o">);</span>
            <span class="n">removePointersFromTouchTargets</span><span class="o">(</span><span class="n">idBitsToRemove</span><span class="o">);</span>
        <span class="o">}</span>
    <span class="o">}</span>

    <span class="k">if</span> <span class="o">(!</span><span class="n">handled</span> <span class="o">&amp;&amp;</span> <span class="n">mInputEventConsistencyVerifier</span> <span class="o">!=</span> <span class="kc">null</span><span class="o">)</span> <span class="o">{</span>
        <span class="n">mInputEventConsistencyVerifier</span><span class="o">.</span><span class="na">onUnhandledEvent</span><span class="o">(</span><span class="n">ev</span><span class="o">,</span> <span class="mi">1</span><span class="o">);</span>
    <span class="o">}</span>
    <span class="k">return</span> <span class="n">handled</span><span class="o">;</span>
<span class="o">}</span>
</code></pre>
</div>

<h2 id="核心要点">核心要点</h2>

<ol>
  <li><strong>事件分发原理: 责任链模式，事件层层传递，直到被消费。</strong></li>
  <li><strong>View 的 <code class="highlighter-rouge">dispatchTouchEvent</code> 主要用于调度自身的监听器和 onTouchEvent。</strong></li>
  <li><strong>View的事件的调度顺序是 onTouchListener &gt; onTouchEvent &gt; onLongClickListener &gt; onClickListener 。</strong></li>
  <li><strong>不论 View 自身是否注册点击事件，只要 View 是可点击的就会消费事件。</strong></li>
  <li><strong>事件是否被消费由返回值决定，true 表示消费，false 表示不消费，与是否使用了事件无关。</strong></li>
  <li><strong>ViewGroup 中可能有多个 ChildView 时，将事件分配给包含点击位置的 ChildView。</strong></li>
  <li><strong>ViewGroup 和 ChildView 同时注册了事件监听器(onClick等)，由 ChildView 消费。</strong></li>
  <li><strong>一次触摸流程中产生事件应被同一 View 消费，全部接收或者全部拒绝。</strong></li>
  <li><strong>只要接受 ACTION_DOWN 就意味着接受所有的事件，拒绝 ACTION_DOWN 则不会收到后续内容。</strong></li>
  <li><strong>如果当前正在处理的事件被上层 View 拦截，会收到一个 ACTION_CANCEL，后续事件不会再传递过来</strong>。</li>
</ol>

<h2 id="总结">总结</h2>

<p>本文啰嗦了这么多内容，但真正需要注意的就是核心要点中的几个概念，只要能正确理解这些概念，相信理解事件分发机制将再也不是难题。</p>

<blockquote>
  <p>最后，个人推荐阅读源码的方法，先尝试用自己的角度去分析，建立概念，然后看源码进行验证、对比，如果发现自己建立的概念有问题，就尝试修正自己的概念，这样比较容易理解原作者的意图，也不容易被众多的代码所迷惑。</p>

  <p>就像 ViewGroup 中的 dispatchTouchEvent 内容非常多，主要是为了应对实际的场景，里面有很多 安全判断，处理多指触控 等内容，这些如果不先建立概念就去看源码很容易被这些细节问题所迷惑。</p>
</blockquote>

<p>由于个人水平有限，文章中可能会出现错误，如果你觉得哪一部分有错误，或者发现了错别字等内容，欢迎在评论区告诉我，另外，据说关注 <a href="http://weibo.com/GcsSloop">作者微博</a> 不仅能第一时间收到新文章消息，还能变帅哦。</p>

<h2 id="参考资料">参考资料</h2>

<p><a href="https://android.googlesource.com/platform/frameworks/base/+/master/core/java/android/view/View.java">View </a><br />
<a href="https://android.googlesource.com/platform/frameworks/base/+/master/core/java/android/view/ViewGroup.java">ViewGroup.java</a><br />
<a href="https://github.com/CharonChui/AndroidNote/blob/master/Android%E5%8A%A0%E5%BC%BA/Android%20Touch%E4%BA%8B%E4%BB%B6%E5%88%86%E5%8F%91%E8%AF%A6%E8%A7%A3.md">Android Touch事件分发详解</a><br />
<a href="http://minjie.tech/2016/09/03/%E5%9F%BA%E4%BA%8E%E6%BA%90%E7%A0%81%E6%9D%A5%E4%BA%86%E8%A7%A3Android%E7%9A%84%E4%BA%8B%E4%BB%B6%E5%88%86%E5%8F%91%E6%9C%BA%E5%88%B6/">基于源码来了解Android的事件分发机制</a></p>

<h2 id="about-me">About Me</h2>

<h3 id="作者微博-gcssloop">作者微博: <a href="http://weibo.com/GcsSloop" target="_blank">@GcsSloop</a></h3>

<p><a href="http://www.gcssloop.com/info/about" target="_blank"><img src="http://gcsblog.oss-cn-shanghai.aliyuncs.com/blog/gcs_banner.jpg?gcssloop" width="300" style="display:inline;" /></a></p>


    <hr>
  </section>
</article>

<!--广告-->

<!--
<div>
<a href="https://m.aliyun.com/act/team1111/?spm=5176.11533457.1089570.5.424777e3AF8WOJ&userCode=hn5smxtw#/" target="_blank"><img src="/assets/aliyun/1111-980-120.jpg" width="100%"></a>
</div>
-->
<!--捐赠晶石-->
<section class="contribute">
    <script type="text/javascript"> 
      function toggle() {
        var con = document.getElementById("contribute");
        if (con.style.display == "none") {
          con.style.display = "block";
        } else {
          con.style.display = "none";
        }
      }
    </script> 
    <blockquote style="background-color:#F5F5F5; padding: 10px 20px 20px 10px; margin:0px" >
      <h4> 如果你觉得我的文章对你有帮助的话，欢迎赞助一些服务器费用! </h4>
      <p></p>
      <a id=“btn-toggle-contribute” class="btn-contribute" onclick="toggle()" >¥ 点击赞助</a>
      <br>
      <div id="contribute" style="display:none;">
        <p align="center" >
        <img src="/assets/images/wechat.png" alt="微信">
        <img src="/assets/images/alipay.png" alt="支付宝">
        </p>
        <p align="left" >
          <b>感谢所有支持我的魔法师，所有支持过我的魔法师都可以通过微信(GcsSloop)联系我，获赠我的付费专栏！</b>
          <!--
          <a href="/contribute">点击这里查看捐赠者名单。</a>
          -->
        </p>
      </div>
    </blockquote>
</section>
<div>
  <h2>欢迎关注我的微信公众号</h2>
  <img src="/assets/images/banner.jpg" width="100%">
</div>

<!--阅读更多-->
<section class="read-more">
  
  
  <div class="read-more-item">
    <span class="read-more-item-dim">最近的文章</span>
    <h2 class="post-list__post-title post-title"><a href="/markdown/markdown-start" title="link to Markdown实用技巧－快速入门">Markdown实用技巧－快速入门</a></h2>
    <p class="excerpt">本文为 Markdown实用技巧 系列的基础文章，并没有不可描述的内容 (⊙ω⊙)自从接触了 Markdown 之后，就一直用 Markdown 作为自己的主要书写工具，不论是平时做一些简单的纪...&hellip;</p>
    <div class="post-list__meta">
      <time datetime="2016-10-24 00:00:00 +0800" class="post-list__meta--date date">2016-10-24</time> &#8226; <span class="post-list__meta--tags tags">Markdown</span>
      <br/><br/>
      <a style="float:none; margin:0 auto;" class="btn-border-small" href=/markdown/markdown-start>继续阅读</a></div>
   </div>
   
   
   
   
   <div class="read-more-item">
       <span class="read-more-item-dim">更早的文章</span>
       <h2 class="post-list__post-title post-title"><a href="/python/tinypng-voice" title="link to 让脚本说话"厉害了我的哥"">让脚本说话"厉害了我的哥"</a></h2>
       <p class="excerpt">前段时间发现 Mac 上一个有趣的方法可以让系统说话。例如在终端输入：say "厉害了我的哥"于是把我自己经常使用的图片压缩脚本升了一下级，人工(智能)语音提示，开启装逼新时代!  本次升级进行...&hellip;</p>
       <div class="post-list__meta">
          <time datetime="2016-10-10 00:00:00 +0800" class="post-list__meta--date date">2016-10-10</time> &#8226; <span class="post-list__meta--tags tags">Python</span>
          <br/><br/>
          <a style="float:none; margin:0 auto;" class="btn-border-small" href=/python/tinypng-voice>继续阅读</a>
       </div>
   </div>
   
</section>

<!--网易云跟帖-->
<!--
<div id="cloud-tie-wrapper" class="cloud-tie-wrapper"></div>
<script src="https://img1.cache.netease.com/f2e/tie/yun/sdk/loader.js"></script>
<script>
var cloudTieConfig = {
  url: document.location.href, 
  sourceId: "",
  productKey: "a85dba2840134721a7b69a15b2e0f217",
  target: "cloud-tie-wrapper"
};
var yunManualLoad = true;
Tie.loader("aHR0cHM6Ly9hcGkuZ2VudGllLjE2My5jb20vcGMvbGl2ZXNjcmlwdC5odG1s", true);
</script>
-->

<style type="text/css">
.isso-comment > div.avatar {
    border: 0px;
    box-shadow: none;
    display: block;
    float: left;
    width: 7%;
    margin: 3px 15px 0 0;
}
.isso-postbox > .form-wrapper > .auth-section .post-action > input {
    border-radius: 6px;
    padding: 6px;
    padding-left: 16px;
    padding-right: 16px;
    border: 1px solid #CCC;
    background-color: #D58D44;
    cursor: pointer;
    outline: 0;
    color: #fff;
    size: 10;
    line-height: 1.4em;
    box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
}
.isso-postbox > .form-wrapper > .auth-section .post-action > input:hover {
    background-color: #272822;
}
.isso-postbox > .form-wrapper > .auth-section .post-action > input:active {
    background-color: #986530;
}
</style>

<section id="isso-thread"></section>

<script data-isso="//47.52.58.34:1234/"
        data-isso-css="true"
        data-isso-lang="zh"
        data-isso-reply-to-self="false"
        data-isso-require-author="false"
        data-isso-require-email="false"
        data-isso-max-comments-top="10"
        data-isso-max-comments-nested="5"
        data-isso-reveal-on-click="5"
        data-isso-avatar="true"
        data-isso-avatar-bg="#f0f0f0"
        data-isso-avatar-fg="#9abf88 #5698c4 #e279a3 #9163b6 ..."
        data-isso-vote="true"
        data-vote-levels=""
        src="//47.52.58.34:1234/js/embed.min.js">
        </script>

<!--
<script async src="//pagead2.googlesyndication.com/pagead/js/adsbygoogle.js"></script>
-->
<!-- OneV's Den -->
<!--
<ins class="adsbygoogle"
     style="display:block"
     data-ad-client="ca-pub-3324997515191619"
     data-ad-slot="9170309685"
     data-ad-format="auto"></ins>
<script>
(adsbygoogle = window.adsbygoogle || []).push({});
</script>
-->

            <section class="footer">
    <footer>
    	<span class="footer__copyright">本站点采用<a rel="license" href="https://creativecommons.org/licenses/by-nc-nd/4.0/deed.zh" target="_blank">知识共享 署名-非商业性使用-禁止演绎 4.0 国际 许可协议</a></span>
        <span class="footer__copyright">本站由 <a href="http://www.GcsSloop.com">@GcsSloop</a> 创建，采用 <a href="https://github.com/GcsSloop/Gcs-Vno-Jekyll" target="_blank">Gcs-Vno-Jekyll</a> 作为主题。<span id="busuanzi_container_site_pv"> 总访问量 <span id="busuanzi_value_site_pv"></span> 次</span> - &copy; 2019</span>
        <span class="footer__sitemap, footer__copyright"><a href="http://www.gcssloop.com/sitemap.xml" target="_blank">Site Map</a>
        <a href="http://www.gcssloop.com/vip" target="_blank">vip</a></span>
    </footer>
</section>

        </div>
    </div>
    
    <script type="text/javascript" src="//code.jquery.com/jquery-1.11.3.min.js"></script>

<script type="text/javascript" src="/js/highlight.pack.js"></script>
<script>hljs.initHighlightingOnLoad();</script>

<script type="text/javascript" src="/js/main.js"></script>

<script>
  (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
  (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
  m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
  })(window,document,'script','https://www.google-analytics.com/analytics.js','ga');

  ga('create', 'UA-82493667-1', 'auto');
  ga('send', 'pageview');

</script>

    
  </body>

</html>
